/*******************************************************************************
 *
 *    Copyright (c) Baina Info Tech Co. Ltd
 *
 *    Snappy
 * 
 *    StringUtil
 *
 *    @author: chzhong
 *    @since:  2010-6-9
 *    @version: 1.0
 *
 ******************************************************************************/

package com.sdk.formatter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.protocol.HTTP;
import org.json.JSONObject;

import android.content.Context;
import android.content.res.AssetManager;
import android.net.Uri;
import android.text.TextUtils;
import android.text.format.Formatter;



/**
 * 文本工具类。
 * 
 * @author chzhong
 * 
 */
public final class StringUtil {

    public static final int MAX_COMMENT_LENGTH = 1000;
    public static final int MAX_PASSWORD_LENGTH = 100;
    public static final int MIN_PASSWORD_LENGTH = 6;
    private static final Pattern NICK_NAME_PATTERN = Pattern.compile("^[_A-Za-z][_A-Za-z0-9]{2,14}$", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
    private static final Pattern NICK_NAME_RESERVED_PATTERN = Pattern.compile(".*Tapler.*|.*Admin.*|.*System.*|.*Mobo\\W*Tap.*|.*Mobo\\W*Square.*", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);

    private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}$", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);

    private static final long ONE_GB = 1L << 30;
    private static final long ONE_MB = 1L << 20;
    private static final long ONE_KB = 1L << 10;

    private static final long GB_UNIT_THREDHOLD = 1000000000;
    private static final long MB_UNIT_THREDHOLD = 1000000;
    private static final long KB_UNIT_THREDHOLD = 1000;
    
    public static final long ONE_M = 1048576;
    public static final long TEN_M = 10485760;
    public static final long HUNDRED_M = 104857600;
    public static final long ONE_G = 1073741824;
    
    
    public static final long TEN_MINUTE = 600;
    public static final long ONE_HOUR = 3600;

    private static final String GB_UNIT = "G";
    private static final String MB_UNIT = "M";
    private static final String KB_UNIT = "K";
    private static final String BYTES_UNIT = "Byte(s)";

    private static final String SIZE_FORMAT = "%,.2f %s";
    private static final String SIZE_FORMAT_WITHOUT_DIGIT = "%,.0f %s";

    public static final String LOGO_URL = "http://www.mobosquare.com/developer/features/newsletter";

    /**
     * The max length for file name.
     */
    public static final int MAX_PATH = 256;

    /**
     * Illegal file name chars.
     */
    public static final Pattern ILLEGAL_FILE_NAME_CHARS = Pattern.compile("[\\\\/:*?<>|]+");

    /**
     * 空白字符。
     */
    private static final char[] WhitespaceChars = new char[] { '\u0000', '\u0001',
        '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0007',
        '\u0008', '\u0009', '\n', '\u000b', '\u000c', '\r', '\u000e',
        '\u000f', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014',
        '\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001a',
        '\u001b', '\u001c', '\u001d', '\u001e', '\u001f', '\u0020'

    };
    private static final String TAG = "StringUtil";

    private static final int BUFFER_SIZE = 4096;
    /**
     * 判断给定的字符数组（已排序）中是否包含给定的字符。
     * 
     * @param chars
     *            已经排序的字符数组。
     * @param ch
     *            要检查的字符。
     * @return 如果 ch 存在于 chars 中则返回 true；否则返回 false。
     */
    public static final boolean containsChar(final char[] chars, final char ch) {
        return Arrays.binarySearch(chars, ch) >= 0;
    }

    public static final boolean isLetter(String args){
    	if (null == args) {
			return false;
		}
    	if (97<=args.toLowerCase().charAt(0)&&args.toLowerCase().charAt(0)<=122) {
			return true;
		}
    	return false;
    }
    /**
     * 返回参数中第一个非 {@code null} 的参数。
     * 
     * @param args 要检查的参数。
     * @return 给定的参数中第一个非 {@code null} 的参数；如果参数都为 {@code null}，则返回 {@code null}。
     */
    public static <T> T firstNotNull(final T... args) {
        for (final T object : args) {
            if (object != null) {
                return object;
            }
        }
        return null;
    }


    /**
     * 返回参数中第一个非 {@code null} 的参数。
     * 
     * @param args 要检查的参数。
     * @return 给定的参数中第一个非 {@code null} 的参数；如果参数都为 {@code null}，则返回 {@code null}。
     */
    public static <T extends CharSequence> T firstNotEmpty(final T... args) {
        for (final T object : args) {
            if (!TextUtils.isEmpty(object)) {
                return object;
            }
        }
        return null;
    }

    /**
     * 返回参数中第一个非零的参数。
     * 
     * @param args 要检查的参数。
     * @return 给定的参数中第一个非零的参数；如果参数都为零，则返回零。
     */
    public static int firstNonZeroInt(final int... args) {
        for (final int object : args) {
            if (object != 0) {
                return object;
            }
        }
        return 0;
    }


    /**
     * 返回参数中第一个非负数的参数。
     * 
     * @param args 要检查的参数。
     * @return 给定的参数中第一个非负数；如果参数都为负数，则返回 {@linkplain Integer#MIN_VALUE}。
     */
    public static int firstNonNegativeInt(final int... args) {
        for (final int object : args) {
            if (object >= 0) {
                return object;
            }
        }
        return Integer.MIN_VALUE;
    }


    /**
     * 返回参数中第一个正数的参数。
     * 
     * @param args 要检查的参数。
     * @return 给定的参数中第一个正数；如果参数都为负数或零，则返回零。
     */
    public static int firstPostiveInt(final int... args) {
        for (final int object : args) {
            if (object > 0) {
                return object;
            }
        }
        return 0;
    }


    /**
     * 返回参数中第一个非零的参数。
     * 
     * @param args 要检查的参数。
     * @return 给定的参数中第一个非零的参数；如果参数都为零，则返回零。
     */
    public static long firstNonZeroLong(final long... args) {
        for (final long object : args) {
            if (object != 0) {
                return object;
            }
        }
        return 0;
    }


    /**
     * 返回参数中第一个非负数的参数。
     * 
     * @param args 要检查的参数。
     * @return 给定的参数中第一个非负数；如果参数都为负数，则返回 {@linkplain longeger#MIN_VALUE}。
     */
    public static long firstNonNegativeLong(final long... args) {
        for (final long object : args) {
            if (object >= 0) {
                return object;
            }
        }
        return Long.MIN_VALUE;
    }


    /**
     * 返回参数中第一个正数的参数。
     * 
     * @param args 要检查的参数。
     * @return 给定的参数中第一个正数；如果参数都为负数或零，则返回零。
     */
    public static long firstPostiveLong(final long... args) {
        for (final long object : args) {
            if (object > 0) {
                return object;
            }
        }
        return 0;
    }


    /**
     * 返回参数中第一个非零的参数。
     * 
     * @param args 要检查的参数。
     * @return 给定的参数中第一个非零的参数；如果参数都为零，则返回零。
     */
    public static double firstNonZero(final double... args) {
        for (final double object : args) {
            if (object != 0) {
                return object;
            }
        }
        return 0;
    }


    /**
     * 返回参数中第一个数的参数。
     * 
     * @param args 要检查的参数。
     * @return 给定的参数中第一个数；如果参数都为 {@linkplain Double#NaN}，则返回 {@linkplain Double#NaN}。
     */
    public static double firstDouble(final double... args) {
        for (final double object : args) {
            if (!Double.isNaN(object)) {
                return object;
            }
        }
        return Double.NaN;
    }



    /**
     * 返回参数中第一个有穷数的参数。
     * 
     * @param args 要检查的参数。
     * @return 给定的参数中第一个有穷数；如果参数都为 {@linkplain Double#NaN} 或者无穷，则返回 {@linkplain Double#NaN}。
     */
    public static double firstFinite(final double... args) {
        for (final double object : args) {
            if (!Double.isNaN(object) && !Double.isInfinite(object)) {
                return object;
            }
        }
        return Double.NaN;
    }

    /**
     * 返回参数中第一个非负数的参数。
     * 
     * @param args 要检查的参数。
     * @return 给定的参数中第一个非负数；如果参数都为负数，则返回 {@linkplain Double#NaN}。
     */
    public static double firstNonNegative(final double... args) {
        for (final double object : args) {
            if (object >= 0) {
                return object;
            }
        }
        return Double.NaN;
    }


    /**
     * 返回参数中第一个正数的参数。
     * 
     * @param args 要检查的参数。
     * @return 给定的参数中第一个正数；如果参数都为负数或零，则返回零。
     */
    public static double firstPostive(final double... args) {
        for (final double object : args) {
            if (object > 0) {
                return object;
            }
        }
        return 0;
    }

    /**
     * 将给定的时间格式化为 XSD/SOAP 的格式。
     * 
     * @param date
     *            要格式化的日期。
     * @return 该日期的 yyyy-MM-ddTHH:mm:ss+ZZ:ZZ 的格式。
     * @deprecated 请使用 {@link com.mobosquare.util.mgeek.android.util.XmlUtil#dateToXsd(Date)} ，它提供了相同的功能。 方法。
     */
    @Deprecated
    public static final String formatDate(final Date date) {
        final String dateTime = String.format("%1$tFT%1$tT", date);
        final String zone = String.format("%1$tz", date);
        return dateTime + zone.substring(0, 3) + ":" + zone.substring(3);
    }

    /**
     * 获取一个类完全限定名的简单名。
     * @param className 类的完全限定名。
     * @return className 的简单名。
     */
    public static final String getSimpleClassName(final String className) {
        if(isNullOrEmpty(className)) {
            return className;
        }
        final int index = className.lastIndexOf('.');
        if(-1 == index) {
            return className.substring(index);
        }
        return className;
    }

    /**
     * 获取异常信息的堆栈跟踪信息。
     * 
     * @param throwable
     *            要解析的异常信息。
     * @return 该异常信息的堆栈跟踪信息。
     */
    public static final String getThrowableStackTrace(final Throwable throwable) {
        final StringWriter sWriter = new StringWriter();
        final PrintWriter printWriter = new PrintWriter(sWriter);
        throwable.printStackTrace(printWriter);
        printWriter.flush();
        printWriter.close();
        final String message = sWriter.toString();
        return message;
    }

    /**
     * 获取给定字符串中，某些特定字符的第一个索引。
     * 
     * @param value
     *            要搜索的字符串。
     * @param chars
     *            要查找的字符。
     * @return value 中第一个出现的 chars 中任意字符的索引。 如果 value 中不含有 chars 中的任意字符，则返回 -1。
     */
    public static int indexOfAny(final String value, final char... chars) {
        return indexOfAny(value, 0, chars);
    }

    /**
     * 获取给定字符串中，某些特定字符的第一个索引。
     * 
     * @param value
     *            要搜索的字符串。
     * @param chars
     *            要查找的字符。
     * @param start
     *            查找的起始点。
     * @return value 中，从 start 起第一个出现的 chars 中任意字符的索引。 如果 value 中不含有 chars
     *         中的任意字符，则返回 -1。
     */
    public static int indexOfAny(final String value, final int start, final char... chars) {
        if (null == value || value.length() == 0) {
            return -1;
        }
        final int i = 0, n = chars.length;
        int index = -1;
        while (i < n || -1 == index) {
            index = value.indexOf(chars[i], start);
        }
        return index;
    }

    /**
     * 判断一个字符串是否为 ""。
     * 
     * @param 待检查的字符串
     *            。
     * @return 如果字符串为 ""，则返回 true；否则返回 false。
     */
    public static final boolean isEmpty(final String value) {
        return null != value && 0 == value.length();
    }

    /**
     * 判断一个字符串是否为 null 或者 ""。
     * 
     * @param 待检查的字符串
     *            。
     * @return 如果字符串为 null 或者 ""，则返回 true；否则返回 false。
     */
    public static final boolean isNullOrEmpty(final String value) {
        return TextUtils.isEmpty(value);
    }

    /**
     * 判断一个字符串是否为 null 或者只包含空白字符（空格、回车、换行等）。
     * 
     * @param 待检查的字符串
     *            。
     * @return 如果字符串为 null 或者只包含空白字符，则返回 true；否则返回 false。
     */
    public static final boolean isNullOrWhitespaces(final String value) {
        return null == value || 0 == value.trim().length();
    }

    /**
     * 判断一个字符串是否只包含空白字符（空格、回车、换行等）。
     * 
     * @param 待检查的字符串
     *            。
     * @return 如果字符串只包含空白字符，则返回 true；否则返回 false。
     */
    public static final boolean isWhitespaces(final String value) {
        return null != value && 0 == value.trim().length();
    }

    /**
     * 获取给定字符串中，某些特定字符的最后一个索引。
     * 
     * @param value
     *            要搜索的字符串。
     * @param chars
     *            要查找的字符。
     * @return value 中最后一个出现的 chars 中任意字符的索引。 如果 value 中不含有 chars 中的任意字符，则返回 -1。
     */
    public static final int lastIndexOfAny(final String value, final char... chars) {
        return lastIndexOfAny(value, value.length() - 1, chars);
    }

    /**
     * 获取给定字符串中，某些特定字符的最后一个索引。
     * 
     * @param value
     *            要搜索的字符串。
     * @param chars
     *            要查找的字符。
     * @param start
     *            查找的起始点，从这个索引开始向前搜索。
     * @return value 中，从 start 起最后一个出现的 chars 中任意字符的索引。 如果 value 中不含有 chars
     *         中的任意字符，则返回 -1。
     */
    public static final int lastIndexOfAny(final String value, final int start, final char... chars) {
        if (null == value || value.length() == 0) {
            return -1;
        }
        final int i = 0, n = chars.length;
        int index = -1;
        while (i < n || -1 == index) {
            index = value.lastIndexOf(chars[i], start);
        }
        return index;
    }

    /**
     * 规范化字符串。
     * @param s 要规范化的字符串。
     * @return 如果字符串为 null 或者长度大于零，则返回 s；如果字符串长度为零，则返回 null。
     */
    public final static String normalize(final String s) {
        if (s == null || s.length() > 0) {
            return s;
        }
        return null;
    }

    /**
     * 以 UTF-8 编码读取一个 Assert 文件的内容。
     * @param context 当前应用程序的上下文。
     * @param fileName 要读取的 Assert 文件名。
     * @return 如果成功，返回 Assert 文件的内容；否则返回 null。
     */
    public static String stringFromAssert(final Context context, final String fileName) {
        String content = null;
        try {
            final AssetManager asserts = context.getAssets();
            final InputStream myInput = asserts.open(fileName);
            final byte[] boe = new byte[3];
            myInput.read(boe, 0, boe.length);
            if (!BitUtil.boeIsUtf8(boe)) {
                myInput.reset();
            }
            content = StringUtil
            .stringFromInputStream(myInput, "utf8");
            myInput.close();
        } catch (final Exception e) {
            e.printStackTrace();
        }
        return content;
    }

    /**
     * 以给定的编码读取一个 Assert 文件的内容。
     * @param context 当前应用程序的上下文。
     * @param fileName 要读取的 Assert 文件名。
     * @param encoding 文件的编码方式。如果文件有 BOE，则根据 BOE 确定编码。
     * @return 如果成功，返回 Assert 文件的内容；否则返回 null。
     */
    public static String stringFromAssert(final Context context, final String fileName, String encoding) {
        String content = null;
        try {
            final AssetManager asserts = context.getAssets();
            final InputStream myInput = asserts.open(fileName);
            final byte[] boe = new byte[3];
            myInput.read(boe, 0, boe.length);
            if (BitUtil.boeIsUtf8(boe)) {
                encoding = "utf8";
                myInput.reset();
            } else if(BitUtil.boeIsUnicode(boe)) {
                encoding = "Unicode";
                myInput.reset();
                myInput.skip(2);
            }
            content = StringUtil
            .stringFromInputStream(myInput, encoding);
            myInput.close();
        } catch (final Exception e) {
            e.printStackTrace();
        }
        return content;
    }

    /**
     * 读取缓存读取器中的内容。
     * @param bufferedReader 要读取的缓冲读取器。
     * @return 存读取器中的内容。
     */
    public static String stringFromBufferedReader(final BufferedReader bufferedReader) {
        if (bufferedReader == null) {
            return null;
        }
        final StringBuffer result = new StringBuffer();
        String line = null;
        try {
            while ((line = bufferedReader.readLine()) != null) {
                result.append(line);
            }
        } catch (final IOException e) {
            e.printStackTrace();
            return null;
        }
        return result.toString();
    }

    /**
     * 读取一个输入流中的内容，并构造成字符串。
     * @param inputStream 要读取的输入流。
     * @param encoding 流采用的字符编码。
     * @return 流中内容所表示的字符串。
     * @throws UnsupportedEncodingException 不支持此编码。
     */
    public static String stringFromInputStream(final InputStream inputStream, final String encoding) throws UnsupportedEncodingException {
        final InputStreamReader reader = new InputStreamReader(inputStream, encoding);
        return stringFromBufferedReader(new BufferedReader(reader));
    }

    /**
     * 移除给定字符串开头和结尾处的特定字符。
     * 
     * @param value
     *            要修剪的字符串。
     * @param chars
     *            要移除的字符。
     * @return 修剪后的字符串。
     */
    public static String trim(final String value, final char... chars) {
        if (null == value || value.length() == 0) {
            return value;
        }
        Arrays.sort(chars);
        int startIndex = 0;
        int endIndex = value.length() - 1;
        boolean flag = containsChar(chars, value.charAt(startIndex));
        while (flag && startIndex <= endIndex) {
            startIndex++;
            flag = containsChar(chars, value.charAt(startIndex));
        }

        flag = containsChar(chars, value.charAt(endIndex));
        while (flag && startIndex <= endIndex) {
            endIndex--;
            flag = containsChar(chars, value.charAt(endIndex));
        }
        if (startIndex >= endIndex) {
            return "";
        }
        return value.substring(startIndex, endIndex + 1);
    }

    /**
     * 移除给定字符串结尾的空白字符。
     * 
     * @param value
     *            要修剪的字符串。
     * @return 修剪后的字符串。
     */
    public static final String trimEnd(final String value) {
        return trimEnd(value, WhitespaceChars);
    }


    /**
     * 移除给定字符串结尾处的特定字符。
     * 
     * @param value
     *            要修剪的字符串。
     * @param chars
     *            要移除的字符。
     * @return 修剪后的字符串。
     */
    public static String trimEnd(final String value, final char... chars) {
        if (null == value || value.length() == 0) {
            return value;
        }
        Arrays.sort(chars);
        int endIndex = value.length() - 1;
        boolean flag = containsChar(chars, value.charAt(endIndex));
        while (flag) {
            endIndex--;
            flag = containsChar(chars, value.charAt(endIndex));
        }
        if (0 >= endIndex) {
            return "";
        }
        return value.substring(0, endIndex + 1);
    }


    /**
     * 移除给定字符串开头的空白字符。
     * 
     * @param value
     *            要修剪的字符串。
     * @return 修剪后的字符串。
     */
    public static final String trimStart(final String value) {
        return trimStart(value, WhitespaceChars);
    }

    /**
     * 移除给定字符串开头的特定字符。
     * 
     * @param value
     *            要修剪的字符串。
     * @param chars
     *            要移除的字符。
     * @return 修剪后的字符串。
     */
    public static String trimStart(final String value, final char... chars) {
        if (null == value || value.length() == 0) {
            return value;
        }
        Arrays.sort(chars);
        int startIndex = 0;
        boolean flag = containsChar(chars, value.charAt(startIndex));
        while (flag) {
            startIndex++;
            flag = containsChar(chars, value.charAt(startIndex));
        }
        if (startIndex >= value.length()) {
            return "";
        }
        return value.substring(startIndex);
    }

    private StringUtil() {
    }

    /**
     * Convert an {@link InputStream} to String.
     * @param stream the stream that contains data.
     * @param encoding the encoding of the data.
     * @return the result string.
     * @throws IOException an I/O error occurred.
     */
    public static String stringFromInputStream2(final InputStream stream, String encoding) throws IOException {
        if (null == stream) {
            throw new IllegalArgumentException("stream may not be null.");
        }
        if (TextUtils.isEmpty(encoding)) {
            encoding = System.getProperty("file.encoding", "utf-8");
        }
        String result;
        final InputStreamReader reader = new InputStreamReader(stream, encoding);
        final StringWriter writer = new StringWriter();
        final char[] buffer = new char[BUFFER_SIZE];
        int charRead = reader.read(buffer);
        while (charRead > 0) {
            writer.write(buffer, 0, charRead);
            charRead = reader.read(buffer);
        }
        result = writer.toString();
        return result;
    }


    /**
     * Convert an {@link InputStream} to String.
     * @param stream the stream that contains data.
     * @return the result string.
     * @throws IOException an I/O error occurred.
     */
    public static String stringFromInputStream2(final InputStream stream) throws IOException {
        return stringFromInputStream2(stream, "utf-8");
    }

    /**
     * Tiny a given string, return a valid file name.
     * @param fileName the file name to clean.
     * @return the valid file name.
     */
    public static String safeFileName(String fileName) {
        if (TextUtils.isEmpty(fileName)) {
            return "";
        }
        if (fileName.length() > MAX_PATH) {
            fileName = fileName.substring(0, MAX_PATH);
        }
        final Matcher matcher = ILLEGAL_FILE_NAME_CHARS.matcher(fileName);
        fileName = matcher.replaceAll("_");
        return fileName;
    }

    /**
     * Html-encode the string.
     * @param s the string to be encoded
     * @return the encoded string
     */
    public static String htmldecode(String text) {
        text = text.replaceAll("&amp;", "&");
        text = text.replaceAll("&lt;", "<");
        text = text.replaceAll("&gt;", ">");
        text = text.replaceAll("&quot;", "\"");
        text = text.replaceAll("&apos;","\'");
        return text;
    }

    public static final String optString(final JSONObject object, final String key) {
        final Object o = object.opt(key);
        return !JSONObject.NULL.equals(o) ? o.toString() : null;
    }

    /**
     * Error message presented when a user tries to treat an opaque URI as
     * hierarchical.
     */
    private static final String NOT_HIERARCHICAL
    = "This isn't a hierarchical URI.";

    /**
     * Get query parameter from {@linkplain Uri} correctly.
     * @param uri the {@linkplain Uri}
     * @param key the query key.
     * @return the decode query value of with key, or {@code null} if not found.
     */
    public static final String getQueryParameter(final Uri uri, final String key) {
        if (null == uri) {
            throw new NullPointerException("uri");
        }
        if (uri.isOpaque()) {
            throw new UnsupportedOperationException(NOT_HIERARCHICAL);
        }
        if (key == null) {
            throw new NullPointerException("key");
        }

        final String query = uri.getEncodedQuery();
        if (query == null) {
            return null;
        }

        final String encodedKey = Uri.encode(key, null);
        final int encodedKeyLength = encodedKey.length();

        int encodedKeySearchIndex = 0;
        final int encodedKeySearchEnd = query.length() - (encodedKeyLength + 1);

        while (encodedKeySearchIndex <= encodedKeySearchEnd) {
            final int keyIndex = query.indexOf(encodedKey, encodedKeySearchIndex);
            if (keyIndex == -1) {
                break;
            }
            final int equalsIndex = keyIndex + encodedKeyLength;
            if (equalsIndex >= query.length()) {
                break;
            }
            if (query.charAt(equalsIndex) != '=') {
                encodedKeySearchIndex = equalsIndex + 1;
                continue;
            }
            if (keyIndex == 0 || query.charAt(keyIndex - 1) == '&') {
                int end = query.indexOf('&', equalsIndex);
                if (end == -1) {
                    end = query.length();
                }
                try {
                    return URLDecoder.decode(query.substring(equalsIndex + 1, end), HTTP.UTF_8);
                } catch (final UnsupportedEncodingException e) {
                    // We never get here.
                    return null;
                }
            } else {
                encodedKeySearchIndex = equalsIndex + 1;
            }
        }
        return null;
    }

    /**
     * Searches the query string for parameter values with the given key.
     *
     * @param key which will be encoded
     *
     * @throws UnsupportedOperationException if this isn't a hierarchical URI
     * @throws NullPointerException if key is null
     *
     * @return a list of decoded values
     */
    public List<String> getQueryParameters(final Uri uri, final String key) {
        if (null == uri) {
            throw new NullPointerException("uri");
        }
        if (uri.isOpaque()) {
            throw new UnsupportedOperationException(NOT_HIERARCHICAL);
        }

        String query = uri.getEncodedQuery();
        if (query == null) {
            return Collections.emptyList();
        }

        String encodedKey;
        try {
            encodedKey = URLEncoder.encode(key, HTTP.UTF_8);
        } catch (final UnsupportedEncodingException e) {
            throw new AssertionError(e);
        }

        // Prepend query with "&" making the first parameter the same as the
        // rest.
        query = "&" + query;

        // Parameter prefix.
        final String prefix = "&" + encodedKey + "=";

        final ArrayList<String> values = new ArrayList<String>();

        int start = 0;
        final int length = query.length();
        while (start < length) {
            start = query.indexOf(prefix, start);

            if (start == -1) {
                // No more values.
                break;
            }

            // Move start to start of value.
            start += prefix.length();

            // Find end of value.
            int end = query.indexOf('&', start);
            if (end == -1) {
                end = query.length();
            }

            final String value = query.substring(start, end);
            try {
                values.add(URLDecoder.decode(value, HTTP.UTF_8));
            } catch (final UnsupportedEncodingException e) {
                throw new AssertionError(e);
            }

            start = end;
        }

        return Collections.unmodifiableList(values);
    }


    /**
     * transform file bytes to string with unit
     * @param bytes
     * @return
     */
    public static String formatFileSize(final Context context, final long bytes){
        return Formatter.formatFileSize(context, bytes);
    }

    /**
     * transform file bytes to string with unit
     * @param bytes
     * @return
     */
    public static String formatFileSize2(final long bytes){
        float displayValue = bytes;
        String unit = BYTES_UNIT;
        String format = SIZE_FORMAT_WITHOUT_DIGIT;
        if (bytes > GB_UNIT_THREDHOLD) {
            displayValue /= ONE_GB;
            unit = GB_UNIT;
            format = SIZE_FORMAT;
        } else if (bytes > MB_UNIT_THREDHOLD) {
            displayValue /= ONE_MB;
            unit = MB_UNIT;
            format = SIZE_FORMAT;
        } else if (bytes > KB_UNIT_THREDHOLD) {
            // For KB size, we don't need to display digits.
            displayValue = bytes / ONE_KB;
            unit = KB_UNIT;
            format = SIZE_FORMAT_WITHOUT_DIGIT;
        }
        return String.format(format, displayValue, unit);
    }

    /**
     * Check whether the email address is a valid email address.
     * @param email the email address to check.
     * @return true if the email address is valid, false otherwise.
     */
    public static boolean isEmailValid(final String email) {
        if (TextUtils.isEmpty(email)) {
            return false;
        }
        final Matcher matcher = EMAIL_PATTERN.matcher(email);
        return matcher.matches();
    }

    /**
     * Determine whether two objects are equal.
     * @param <T> the type of the the objects.
     * @param one the first object.
     * @param other the second object.
     */
    public static <T> boolean equals(final T one, final T other) {
        if (one != null && other != null) {
            return one.equals(other);
        }
        return one == other;
    }

    /**
     * Determine whether two strings are equal.
     * @param one the first object.
     * @param other the second object.
     */
    public static boolean equals(final String one, final String other) {
        if (one != null && other != null) {
            return one.equals(other);
        }
        return one == other;
    }

    /**
     * Determine whether two strings are equal, ignoring case.
     * @param one the first object.
     * @param other the second object.
     */
    public static boolean equalsIgnoreCase(final String one, final String other) {
        if (one != null && other != null) {
            return one.equalsIgnoreCase(other);
        }
        return one == other;
    }

    /**
     * Determine whether a nick name is valid.
     * 
     * <p>A valid nick name must match the following rule:</p>
     * <ul>
     * <li>Have a length of 3-15.</li>
     * <li>Contains only underscore(_), letters or digits. </li>
     * <li>Digits can not be used as start of a nick name.</li>
     * <li>Must not contains any reserved words below:</li>
     * <ul>
     * <li>Tapler</li>
     * <li>Admin</li>
     * <li>System</li>
     * <li>MoboTap</li>
     * <li>MoboSquare</li>
     * </ul>
     * </ul>
     * @param nicknName the nick name to check.
     * @return true if the nick name is valid, false otherwise.
     */
    public static boolean isNickNameValid(final String nicknName) {
        return NICK_NAME_PATTERN.matcher(nicknName).matches() && !NICK_NAME_RESERVED_PATTERN.matcher(nicknName).matches();
    }


    /**
     * Determine whether a comment is valid.
     * 
     * <p>A valid comment must match the following rule:</p>
     * <ul>
     * <li>Have a length of 1-140.</li>
     * </ul>
     * @param message the content of the comment.
     * @return true if the comment is valid, false otherwise.
     */
    public static boolean isCommentValid(final String message) {
        return !TextUtils.isEmpty(message) && message.length() <= MAX_COMMENT_LENGTH;
    }

    /**
     * Retrieve string value of a {@linkplain CharSequence}.
     * @param value the {@linkplain CharSequence} which value to retrieve.
     * @return the value of the {@code value}.
     */
    public static String valueOf(final CharSequence value) {
        if (null == value) {
            return null;
        }
        return value.toString();
    }


}
